﻿using System.Collections.Generic;
using System.Management.Automation.Runspaces;

namespace Wolfpack.Contrib.Checks.Powershell
{
    /// <summary>
    /// Wraps and caches the creation of <see cref="RunspaceConfiguration"/>.  Adding
    /// SnapIns to a <see cref="RunspaceConfiguration"/> takes a relatively long
    /// time.
    /// </summary>
    /// <remarks>
    /// Is this actually implementing the factory pattern?
    /// </remarks>
    public class CachingRunspaceConfigurationFactory : RunspaceConfigurationFactoryBase
    {
        private static readonly object _cacheLock;
        private static readonly Dictionary<RunspaceConfigurationCacheKey, RunspaceConfiguration> _cache;

        /// <summary>
        /// Initializes the <see cref="CachingRunspaceConfigurationFactory"/> class.
        /// </summary>
        static CachingRunspaceConfigurationFactory()
        {
            _cacheLock = new object();
            _cache = new Dictionary<RunspaceConfigurationCacheKey, RunspaceConfiguration>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CachingRunspaceConfigurationFactory"/> class.
        /// </summary>
        /// <param name="snapInNames">The snap in names.</param>
        public CachingRunspaceConfigurationFactory(IEnumerable<string> snapInNames)
            : base(snapInNames)
        {
        }

        /// <summary>
        /// Gets the runspace configuration.
        /// </summary>
        /// <returns></returns>
        public override RunspaceConfiguration Create()
        {
            RunspaceConfigurationCacheKey key = new RunspaceConfigurationCacheKey(SnapInNames);

            // check to see if the cache already contains the configuration
            lock (_cacheLock)
            {
                RunspaceConfiguration cachedConfiguration;

                if (_cache.TryGetValue(key, out cachedConfiguration))
                {
                    return cachedConfiguration;
                }
            }

            // calling GetRunspaceConfiguration is the expensive part, however
            // to reduce the lock contension, create the RunspaceConfiguration
            // outside the lock. 
            RunspaceConfiguration configuration = CreateRunspaceConfiguration();

            lock (_cacheLock)
            {
                RunspaceConfiguration cachedConfiguration;

                if (_cache.TryGetValue(key, out cachedConfiguration))
                {
                    // return the value already in the cache
                    // this is very unlikely to occur
                    configuration = cachedConfiguration;
                }
                else
                {
                    _cache.Add(key, configuration);
                }
            }

            return configuration;
        }
    }
}